﻿using System;
using System.Collections;
using System.Linq;
using com.ddmresources.EnumMetadata.Core;
using com.ddmresources.EnumMetadata.Core.Attributes;
using com.ddmresources.EnumMetadata.Samples;
using Microsoft.VisualStudio.TestPlatform.UnitTestFramework;

namespace com.ddmresources.EnumMetadata.Test
{
    /// <summary>
    /// This class contains unit tests that exercise the functionality of the EnumMetadata library. These tests depend on
    /// having access to and using the types (attributes, metadata objects, and enum definitions) contained in the 
    /// EnumMetatdata.Samples project.
    /// </summary>
    [TestClass]
    public class EnumMetadataTests
    {
        #region Utility Classes
        public class ClassWithNullableEnumProperty
        {
            public Nullable<CbiType> Cbi { get; set; }
            public Nullable<ClientStatus> ClientStatus { get; set; }
        }

        #endregion

        #region Info Tests
        [TestMethod]
        public void Info_StaticEnum_Generic_Success()
        {
            Assert.AreEqual("Geographic Area", CbiType.GA.Info<ICodeTableInfo>().Description);
        }

        [TestMethod]
        public void Info_StaticEnum_StronglyTypedHelper_Success()
        {
            Assert.AreEqual("102", HelpTopic.ConstructionView.Info().Id);
        }

        [TestMethod]
        public void Info_DynamicEnum_Generic_Success()
        {
            Assert.AreEqual(570, TrafficLightColor.Yellow.Info<ITrafficLightColorInfo>().Wavelength);            
        }

        [TestMethod]
        public void Info_DynamicEnum_Generic_ChildArray_Success()
        {
            Assert.AreEqual(8, TrafficLightColor.Yellow.Info<ITrafficLightColorInfo>().NTSBRegulations[1]);
        }

        [TestMethod]
        public void Info_DynamicEnum_Generic_ChildObject_Success()
        {
            Assert.AreEqual(103, TrafficLightColor.Green.Info<ITrafficLightColorInfo>().Example.Id);
        }

        [TestMethod]
        public void Info_DynamicEnum_StronglyTypedHelper_Success()
        {
            Assert.AreEqual("Active Status", ClientStatus.Active.Info().Description);
        }

        [TestMethod]
        public void Info_EnumInfoSpecificationMissing_Failure()
        {
            Assert.ThrowsException<InvalidOperationException>(() => MissingEnumInfoSpecification.Value1.Info<IClientStatusInfo>().Code);
        }

        [TestMethod]
        public void Info_MissingMetadataAttributeTypeProperty_Failure()
        {
            Assert.ThrowsException<ArgumentException>(() => MissingMetadatAttributeTypeProperty.Value1.Info<IClientStatusInfo>().Code);
        }

        [TestMethod]
        public void Info_MissingOutputTypeProperty_Failure()
        {
            Assert.ThrowsException<ArgumentException>(() => MissingOutputTypeProperty.Value1.Info<IClientStatusInfo>().Code);
        }

        [TestMethod]
        public void Info_NullEnumInstance_Failure()
        {
            Assert.ThrowsException<ArgumentException>(() =>
                                                            {
                                                                Enum e = null;
                                                                var result = e.Info<IClientStatusInfo>().Description;
                                                            }
                );
        }

        [TestMethod]
        public void Info_InvalidInfoMode_Failure()
        {
            Assert.ThrowsException<ArgumentException>(() => InvalidInfoMode.Value1.Info<IClientStatusInfo>().Code);
        }

        [TestMethod]
        public void Info_MissingDynamicInfoAttribute_Failure()
        {
            Assert.ThrowsException<InvalidOperationException>(() => MissingDynamicInfo.Value1.Info<IClientStatusInfo>().Code);
        }
        #endregion

        #region InfoLax Tests
        [TestMethod]
        public void InfoLax_StaticEnum_Generic_Success()
        {
            Assert.AreEqual("Geographic Area", CbiType.GA.InfoLax<CodeTableInfoAttribute>().Description);
        }

        [TestMethod]
        public void InfoLax_StaticEnum_StronglyTypedHelper_Success()
        {
            Assert.AreEqual("102", HelpTopic.ConstructionView.InfoLax().Id);
        }

        [TestMethod]
        public void InfoLax_DynamicEnum_Generic_Success()
        {
            Assert.AreEqual(570, TrafficLightColor.Yellow.InfoLax<TrafficLightColorInfo>().Wavelength);            
        }

        [TestMethod]
        public void InfoLax_DynamicEnum_Generic_ChildArray_Success()
        {
            Assert.AreEqual(8, TrafficLightColor.Yellow.InfoLax<TrafficLightColorInfo>().NTSBRegulations[1]);
        }

        [TestMethod]
        public void InfoLax_DynamicEnum_Generic_ChildObject_Success()
        {
            Assert.AreEqual(103, TrafficLightColor.Green.InfoLax<TrafficLightColorInfo>().Example.Id);
        }

        [TestMethod]
        public void InfoLax_DynamicEnum_StronglyTypedHelper_Success()
        {
            Assert.AreEqual("Active Status", ClientStatus.Active.InfoLax().Description);
        }

        [TestMethod]
        public void InfoLax_EnumInfoSpecificationMissing_Failure()
        {
            Assert.ThrowsException<InvalidOperationException>(() => MissingEnumInfoSpecification.Value1.InfoLax<ClientStatusInfo>().Code);
        }

        [TestMethod]
        public void InfoLax_MissingMetadataAttributeTypeProperty_Failure()
        {
            Assert.ThrowsException<ArgumentException>(() => MissingMetadatAttributeTypeProperty.Value1.InfoLax<ClientStatusInfo>().Code);
        }

        [TestMethod]
        public void InfoLax_NullEnumInstance_Success()
        {
            Enum e = null;
            var clientStatusInfo = e.InfoLax<ClientStatusInfo>();

            Assert.AreEqual(0, clientStatusInfo.Id);
            Assert.AreEqual(null, clientStatusInfo.Description);
        }

        [TestMethod]
        public void InfoLax_Static_BadEnumValue_Success()
        {
            CbiType badCbi = EnumMetadataHelper.GetEnumInstance<CbiType>(999);

            var codeTableInfo = badCbi.InfoLax<CodeTableInfoAttribute>();
            Assert.AreEqual(codeTableInfo.MappedId, 0);
            Assert.AreEqual(codeTableInfo.MappedCode, null);
        }

        [TestMethod]
        public void InfoLaxLax_Dynamic_BadEnumValue_Success()
        {
            ClientStatus badClientStatus = EnumMetadataHelper.GetEnumInstance<ClientStatus>(999);

            var clientStatusInfo = badClientStatus.InfoLax<ClientStatusInfo>();
            Assert.AreEqual(clientStatusInfo.Code, null);
        }

        [TestMethod]
        public void InfoLax_MissingOutputTypeProperty_Failure()
        {
            Assert.ThrowsException<ArgumentException>(() => MissingOutputTypeProperty.Value1.InfoLax<ClientStatusInfo>().Code);
         }

        [TestMethod]
        public void InfoLax_InvalidInfoMode_Failure()
        {
            Assert.ThrowsException<ArgumentException>(() => InvalidInfoMode.Value1.Info<IClientStatusInfo>().Code);
        }

        [TestMethod]
        public void InfoLax_MissingDynamicInfoAttribute_Failure()
        {
            Assert.ThrowsException<InvalidOperationException>(() => MissingDynamicInfo.Value1.Info<IClientStatusInfo>().Code);
        }
        #endregion

        #region GetEnumInstanceAttribute Tests
        [TestMethod]
        public void GetEnumInstanceAttribute_Static_FullInputs_Success()
        {
            var cbiType = CbiType.UBI;
            var codeTableInfo = (ICodeTableInfo) EnumMetadataHelper.GetEnumInstanceAttribute(cbiType, typeof (CodeTableInfoAttribute));
            Assert.AreEqual(codeTableInfo.MappedId, 8);
        }

        [TestMethod]
        public void GetEnumInstanceAttribute_Static_FilledNullable_Success()
        {
            var classWithNullableEnumProperty = new ClassWithNullableEnumProperty { Cbi = CbiType.UBI };
            var codeTableInfo = (ICodeTableInfo)EnumMetadataHelper.GetEnumInstanceAttribute(classWithNullableEnumProperty.Cbi, typeof(CodeTableInfoAttribute));
            Assert.AreEqual(codeTableInfo.MappedId, 8);
        }

        [TestMethod]
        public void GetEnumInstanceAttribute_Static_EmptyNullable_Failure() 
        {
            Assert.ThrowsException<ArgumentException>(() =>
                                                          {
                                                              var classWithNullableEnumProperty = new ClassWithNullableEnumProperty { Cbi = null };
                                                              var codeTableInfo = (ICodeTableInfo)EnumMetadataHelper.GetEnumInstanceAttribute(classWithNullableEnumProperty.Cbi, typeof(CodeTableInfoAttribute));
                                                          }
                );

         }

        [TestMethod]
        public void GetEnumInstanceAttribute_Static_NoSoughtType_Failure()
        {
            Assert.ThrowsException<ArgumentException>(() =>
                {
                    var classWithNullableEnumProperty = new ClassWithNullableEnumProperty { Cbi = CbiType.UBI };
                    var codeTableInfo = (ICodeTableInfo)EnumMetadataHelper.GetEnumInstanceAttribute(classWithNullableEnumProperty.Cbi, null);
                }
            );


        }

        [TestMethod]
        public void GetEnumInstanceAttribute_Dynamic_FullInputs_Success()
        {
            var clientStatus = ClientStatus.Active;
            var enumDynamicInfoAttribute = (EnumDynamicInfoAttribute)EnumMetadataHelper.GetEnumInstanceAttribute(clientStatus, typeof(EnumDynamicInfoAttribute));
            Assert.AreEqual(enumDynamicInfoAttribute.Info, @"{""Id"": 11, ""Code"":""A"", ""Description"":""Active Status"", ""IsMapped"": true}");
        }

        [TestMethod]
        public void GetEnumInstanceAttribute_Dynamic_FilledNullable_Success()
        {
            var classWithNullableEnumProperty = new ClassWithNullableEnumProperty { ClientStatus = ClientStatus.Active };
            var enumDynamicInfoAttribute = (EnumDynamicInfoAttribute)EnumMetadataHelper.GetEnumInstanceAttribute(classWithNullableEnumProperty.ClientStatus, typeof(EnumDynamicInfoAttribute));
            Assert.AreEqual(enumDynamicInfoAttribute.Info, @"{""Id"": 11, ""Code"":""A"", ""Description"":""Active Status"", ""IsMapped"": true}");
        }

        [TestMethod]
        public void GetEnumInstanceAttribute_Dynamic_EmptyNullable_Failure()
        {
            Assert.ThrowsException<ArgumentException>(() =>
                {
                    var classWithNullableEnumProperty = new ClassWithNullableEnumProperty { ClientStatus = null };
                    var enumDynamicInfoAttribute = (EnumDynamicInfoAttribute)EnumMetadataHelper.GetEnumInstanceAttribute(classWithNullableEnumProperty.ClientStatus, typeof(EnumDynamicInfoAttribute));
                }
            );

        }

        [TestMethod]
        public void GetEnumInstanceAttribute_Dynamic_NoSoughtType_Failure()
        {
            Assert.ThrowsException<ArgumentException>(() =>
                {
                    var classWithNullableEnumProperty = new ClassWithNullableEnumProperty { ClientStatus = ClientStatus.Active };
                    var enumDynamicInfoAttribute = (EnumDynamicInfoAttribute)EnumMetadataHelper.GetEnumInstanceAttribute(classWithNullableEnumProperty.ClientStatus, null);
                }
            );

        }


        #endregion

        #region GetEnumInstanceAttributeLax Tests

        [TestMethod]
        public void GetEnumInstanceAttributeLax_Static_FullInputs_Success()
        {
            var cbiType = CbiType.UBI;
            var codeTableInfo = (ICodeTableInfo)EnumMetadataHelper.GetEnumInstanceAttributeLax(cbiType, typeof(CodeTableInfoAttribute));
            Assert.AreEqual(codeTableInfo.MappedId, 8);
        }

        [TestMethod]
        public void GetEnumInstanceAttributeLax_Static_FilledNullable_Success()
        {
            var classWithNullableEnumProperty = new ClassWithNullableEnumProperty { Cbi = CbiType.UBI };
            var codeTableInfo = (ICodeTableInfo)EnumMetadataHelper.GetEnumInstanceAttributeLax(classWithNullableEnumProperty.Cbi, typeof(CodeTableInfoAttribute));
            Assert.AreEqual(codeTableInfo.MappedId, 8);
        }

        [TestMethod]
        public void GetEnumInstanceAttributeLax_Static_EmptyNullable_Success()
        {
            var classWithNullableEnumProperty = new ClassWithNullableEnumProperty { Cbi = null };
            var codeTableInfo = (ICodeTableInfo)EnumMetadataHelper.GetEnumInstanceAttributeLax(classWithNullableEnumProperty.Cbi, typeof(CodeTableInfoAttribute));
            Assert.AreEqual(codeTableInfo.MappedId, 0);
            Assert.AreEqual(codeTableInfo.MappedCode, null);
        }

        [TestMethod]
        public void GetEnumInstanceAttributeLax_Static_BadEnumValue_Success()
        {
            CbiType badCbi = EnumMetadataHelper.GetEnumInstance<CbiType>(999);
            var classWithNullableEnumProperty = new ClassWithNullableEnumProperty() { Cbi = badCbi };

            var codeTableInfo = (ICodeTableInfo)EnumMetadataHelper.GetEnumInstanceAttributeLax(classWithNullableEnumProperty.Cbi, typeof(CodeTableInfoAttribute));
            Assert.AreEqual(codeTableInfo.MappedId, 0);
            Assert.AreEqual(codeTableInfo.MappedCode, null);
        }

        [TestMethod]
        public void GetEnumInstanceAttributeLax_Static_NoSoughtType_Failure()
        {
            Assert.ThrowsException<ArgumentException>(() =>
                                                          {
                                                              var classWithNullableEnumProperty = new ClassWithNullableEnumProperty { Cbi = CbiType.UBI };
                                                              var codeTableInfo = (ICodeTableInfo)EnumMetadataHelper.GetEnumInstanceAttributeLax(classWithNullableEnumProperty.Cbi, null);
                                                          }
                );

        }

        [TestMethod]
        public void GetEnumInstanceAttributeLax_Dynamic_FullInputs_Success()
        {
            var clientStatus = ClientStatus.Active;
            var enumDynamicInfoAttribute = (EnumDynamicInfoAttribute)EnumMetadataHelper.GetEnumInstanceAttributeLax(clientStatus, typeof(EnumDynamicInfoAttribute));
            Assert.AreEqual(enumDynamicInfoAttribute.Info, @"{""Id"": 11, ""Code"":""A"", ""Description"":""Active Status"", ""IsMapped"": true}");
        }

        [TestMethod]
        public void GetEnumInstanceAttributeLax_Dynamic_FilledNullable_Success()
        {
            var classWithNullableEnumProperty = new ClassWithNullableEnumProperty { ClientStatus = ClientStatus.Active };
            var enumDynamicInfoAttribute = (EnumDynamicInfoAttribute)EnumMetadataHelper.GetEnumInstanceAttributeLax(classWithNullableEnumProperty.ClientStatus, typeof(EnumDynamicInfoAttribute));
            Assert.AreEqual(enumDynamicInfoAttribute.Info, @"{""Id"": 11, ""Code"":""A"", ""Description"":""Active Status"", ""IsMapped"": true}");
        }

        [TestMethod]
        public void GetEnumInstanceAttributeLax_Dynamic_EmptyNullable_Success()
        {
            var classWithNullableEnumProperty = new ClassWithNullableEnumProperty { ClientStatus = null };
            var enumDynamicInfoAttribute = (EnumDynamicInfoAttribute)EnumMetadataHelper.GetEnumInstanceAttributeLax(classWithNullableEnumProperty.ClientStatus, typeof(EnumDynamicInfoAttribute));
            Assert.AreEqual(enumDynamicInfoAttribute.Info, null);
        }

        [TestMethod]
        public void GetEnumInstanceAttributeLax_Dynamic_BadEnumValue_Success()
        {
            ClientStatus badClientStatus = EnumMetadataHelper.GetEnumInstance<ClientStatus>(999);
            var classWithNullableEnumProperty = new ClassWithNullableEnumProperty() { ClientStatus = badClientStatus };
            var enumDynamicInfoAttribute = (EnumDynamicInfoAttribute)EnumMetadataHelper.GetEnumInstanceAttributeLax(classWithNullableEnumProperty.ClientStatus, typeof(EnumDynamicInfoAttribute));
            Assert.AreEqual(enumDynamicInfoAttribute.Info, null);
        }

        [TestMethod]
        public void GetEnumInstanceAttributeLax_Dynamic_NoSoughtType_Failure()
        {
            Assert.ThrowsException<ArgumentException>(() =>
                                                          {
                                                              var classWithNullableEnumProperty = new ClassWithNullableEnumProperty { ClientStatus = ClientStatus.Active };
                                                              var enumDynamicInfoAttribute = (EnumDynamicInfoAttribute)EnumMetadataHelper.GetEnumInstanceAttributeLax(classWithNullableEnumProperty.ClientStatus, null);
                                                          }
                );

         }
        #endregion

        #region GetEnumInstance Tests
        [TestMethod]
        public void GetEnumInstance_Int_Success()
        {
            Assert.AreEqual(CbiType.UBI, EnumMetadataHelper.GetEnumInstance<CbiType>(8));
        }

        [TestMethod]
        public void GetEnumInstance_BadInt_Failure()
        {
            var result = EnumMetadataHelper.GetEnumInstance<CbiType>(1000);
            Assert.AreEqual((CbiType)EnumMetadataHelper.InvalidEnumValueReplacementValue, result); //these will not be equal because we have fed an invalid argument
        }

        [TestMethod]
        public void GetEnumInstance_String_Success()
        {
            Assert.AreEqual(TrafficLightColor.Green, EnumMetadataHelper.GetEnumInstance<TrafficLightColor>("Green"));
        }

        [TestMethod]        
        public void GetEnumInstance_BadString_Failure()
        {

            Assert.AreEqual((TrafficLightColor)EnumMetadataHelper.InvalidEnumValueReplacementValue, EnumMetadataHelper.GetEnumInstance<TrafficLightColor>("BadInput"));
        }
        #endregion

        #region FindEnumInstances Tests
        [TestMethod]
        public void FindEnumInstances_DynamicEnum_SingleMatch_Success()
        {
            var results =
                EnumMetadataHelper.FindEnumInstances<TrafficLightColor, ITrafficLightColorInfo, int>(() => (new TrafficLightColorInfo()).Wavelength, 650);
            Assert.AreEqual(1, results.Count());
            Assert.AreEqual(TrafficLightColor.Red, results.First());
        }

        [TestMethod]
        public void FindEnumInstances_DynamicEnum_MultipleMatch_Success()
        {
            var results =
                EnumMetadataHelper.FindEnumInstances<ClientStatus, IClientStatusInfo, bool>(() => (new ClientStatusInfo()).IsMapped, true);
            Assert.AreEqual(2, results.Count());
            Assert.AreEqual(ClientStatus.Active, results.ElementAt(0));
            Assert.AreEqual(ClientStatus.Inactive, results.ElementAt(1));
        }

        [TestMethod]
        public void FindEnumInstances_DynamicEnum_NoMatch_GetBackEmptyEnumerable_Success()
        {
            var results =
                EnumMetadataHelper.FindEnumInstances<TrafficLightColor, ITrafficLightColorInfo, int>(() => (new TrafficLightColorInfo()).Wavelength, -99);

            Assert.IsInstanceOfType(results, typeof(IEnumerable));
            Assert.AreEqual(0, results.Count());
           
        }

        [TestMethod]
        public void FindEnumInstances_StaticEnum_SingleMatch_Success()
        {
            var results =
                EnumMetadataHelper.FindEnumInstances<CbiType, ICodeTableInfo, string>(() => (new CodeTableInfoAttribute()).MappedCode, "NL");
            Assert.AreEqual(1, results.Count());
            Assert.AreEqual(CbiType.NL, results.First());
        }

        [TestMethod]
        public void FindEnumInstances_StaticEnum_MultipleMatch_Success()
        {
            var results =
                EnumMetadataHelper.FindEnumInstances<HelpTopic, IHelpTopicInfo, bool>(() => (new HelpTopicInfoAttribute()).IsContentCrossCompany, true);
            Assert.AreEqual(3, results.Count());
            Assert.AreEqual(HelpTopic.AccountView, results.ElementAt(1));
            Assert.AreEqual(HelpTopic.LocationsView, results.ElementAt(2));
        }

        [TestMethod]
        public void FindEnumInstances_StaticEnum_NoMatch_GetBackEmptyEnumerable_Success()
        {
            var results =
                EnumMetadataHelper.FindEnumInstances<HelpTopic, IHelpTopicInfo, string>(() => (new HelpTopicInfoAttribute()).SkinName, "BadData");

            Assert.IsInstanceOfType(results, typeof(IEnumerable));
            Assert.AreEqual(0, results.Count());

        }
        #endregion

        #region ExtractEnumList Tests
        [TestMethod]
        public void ExtractEnumList_StaticEnum_Success()
        {
            var comboList =
                EnumMetadataHelper.ExtractEnumList<CbiType, ICodeTableInfo, string>(
                    () => (new CodeTableInfoAttribute()).Description);

            var description = (comboList.Single(kvp => kvp.Key == CbiType.UBI)).Value;

            Assert.AreEqual<string>("Unreported Business Interest", description);
        }

        [TestMethod]
        public void ExtractEnumList_StaticEnum_FromMatchList_Success()
        {
            var matchList =
                EnumMetadataHelper.FindEnumInstances<HelpTopic, IHelpTopicInfo, bool>(() => (new HelpTopicInfoAttribute()).IsContentCrossCompany, false);

            var comboList =
                EnumMetadataHelper.ExtractEnumList<HelpTopic, IHelpTopicInfo, string>(
                    () => (new HelpTopicInfoAttribute()).Id, matchList);

            var id = (comboList.Single(kvp => kvp.Key == HelpTopic.EngineeringView)).Value;

            Assert.AreEqual(2, comboList.Count);
            Assert.AreEqual(HelpTopic.EngineeringView, comboList.ElementAt(0).Key);            
            Assert.AreEqual<string>("101", id);
        }


        [TestMethod]
        public void ExtractEnumList_DynamicEnum_Success()
        {
            var comboList =
                EnumMetadataHelper.ExtractEnumList<TrafficLightColor, ITrafficLightColorInfo, string>(
                    () => (new TrafficLightColorInfo()).IntendedAction);

            var description = (comboList.Single(kvp => kvp.Key == TrafficLightColor.Green)).Value;

            Assert.AreEqual<string>("Proceed", description);

        }

        [TestMethod]
        public void ExtractEnumList_DynamicEnum_FromMatchList_Success()
        {
            var matchList =
                EnumMetadataHelper.FindEnumInstances<TrafficLightColor, ITrafficLightColorInfo, bool>(() => (new TrafficLightColorInfo()).IsColorBlind, true);

            var comboList =
                EnumMetadataHelper.ExtractEnumList<TrafficLightColor, ITrafficLightColorInfo, string>(
                    () => (new TrafficLightColorInfo()).IntendedAction, matchList);

            var intendedAction = (comboList.Single(kvp => kvp.Key == TrafficLightColor.Green)).Value;

            Assert.AreEqual(1, comboList.Count);
            Assert.AreEqual(TrafficLightColor.Green, comboList.ElementAt(0).Key);
            Assert.AreEqual<string>("Proceed", intendedAction);
        }
        #endregion
    }
}
